package pro.ticktock.tools.mbconv;

import pro.ticktock.tools.mbconv.AbstractPass;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by lambert on 2017/5/29.
 */
public class Pass6 extends AbstractPass {

  public static final String LIST_MARK = "###LIST###";
  public static final String NUMBER_NA_MARK = "###NUMBER_NA###";
  public static final String LEADING_LIST_MARK = "###LEADING_LIST###";
  protected Pass6(String origSrcFileName, String srcFileName) throws IOException {
    super(origSrcFileName, srcFileName);
  }

  int incremental = 0;
  int nonIncremental = 0;
  int hitAlgorithm1 = 0;
  int hitAlgorithm2 = 0;
  int hitAlgorithm3 = 0;
  int hitAlgorithm4 = 0;
  int srcErrorCount = 0;
  int nonhit = 0;

  StringBuffer al1Titles = new StringBuffer();
  StringBuffer al2Titles = new StringBuffer();
  StringBuffer al3Titles = new StringBuffer();
  StringBuffer al4Titles = new StringBuffer();
  StringBuffer nonHitTitles = new StringBuffer();
  StringBuffer srcErrorTitles = new StringBuffer();
  List<Integer> numList;

  @Override
  protected void doProcess() throws IOException {
    String line;
    String title = null;
    while ((line = srcReader.readLine()) != null) {
      if (line.startsWith("###CH") || line.startsWith("###EN")) {
        if(line.startsWith("###CH"))
          title = line.substring(line.indexOf("###", 5) + 3);
        resultWriter.write(line + "\n");
        continue;
      }

      boolean formated = false;
      numList = getNumberList(line);
      if (numList.size() < 2) {
        resultWriter.write(line + "\n");
        continue;
      } else {
        if (isIncremental(numList)) {
          incremental++;

          resultWriter.append(line.substring(0, offsets.get(0)));
          int max = numList.get(numList.size() - 1);
          for (int i = 0; i < max; i++) {
            resultWriter.append(LIST_MARK);
            resultWriter.append(NUMBER_NA_MARK); //safe to remove this debugging list order
            if (i == 0) {
              resultWriter.append(LEADING_LIST_MARK);
            }
            int len = String.valueOf(i + 1).length();
            if (i == max - 1) { //last
              resultWriter.append(line.substring(offsets.get(i) + len + 1) + "\n");
            } else {
              resultWriter.append(line.substring(offsets.get(i) + len + 1, offsets.get(i + 1)));
            }
          }
          formated = true;
        } else {
          logWriter.append("Non incremental list numbers: " + numList + ", line: " + line + "\n");
          nonIncremental++;

          //source error, such as 2,3,4, or 1,2,3,5,6
          boolean srcError = true;
          for (int i = 1; i < numList.size(); i++) {
            if (numList.get(i) >= numList.get(i - 1)) {
              continue;
            } else {
              srcError = false;
            }
          }

          if (srcError) {
            srcErrorCount++;
            srcErrorTitles.append(title + ", list numbers: " + numList + "\n");
            resultWriter.write(line + "\n");
            continue;
          } else {
            Integer[] charPriority = new Integer[numList.size() + 1];

            int cyclePriority = 0;
            int listNumOffset = 0;
            int nextListNumOffset = offsets.get(listNumOffset);
            for (int i = 0; i < line.length(); i++) {
              char ch = line.charAt(i);
              int currPriority = getCharPriority(ch);
              if (currPriority > cyclePriority) {
                cyclePriority = currPriority;
              }

              if (i == nextListNumOffset) {
                charPriority[listNumOffset] = cyclePriority;
                cyclePriority = 0;
                i += String.valueOf(numList.get(listNumOffset)).length() + 1;
                listNumOffset++;
                if (listNumOffset < numList.size()) {
                  nextListNumOffset = offsets.get(listNumOffset);
                }
              }
            }

            charPriority[listNumOffset] = cyclePriority;

            //algorithm 1: split by biggest priority
            int max = Arrays.stream(Arrays.copyOfRange(charPriority, 1, charPriority.length - 1)).max((a, b) -> a - b).get();
            List<Integer> childList = new ArrayList<>();
            boolean success = true;
            List<Integer> delimiters = new ArrayList<>();
            for (int i = 1; i < charPriority.length; i++) {
              childList.add(numList.get(i - 1));
              if (charPriority[i] == max || i == charPriority.length - 1) {
                if (childList.size() >= 2 && childList.get(0) == 1
                        && childList.get(childList.size() - 1) == childList.size()) {
                } else { //failed!!!
                  success = false;
                  break;
                }
                delimiters.add(childList.get(childList.size() - 1));
                childList.clear();
              }
            }
            if (success) {
              hitAlgorithm1++;
              al1Titles.append(title + "\n");

              if (delimiters.size() < 2) { //shall not happend
                logWriter.append("Algorithm 1 but supplied with only one delimiter.");
              }

              StringBuffer tmpBuf = new StringBuffer();
              lw : while (true) {
                int startIdx = 0;
                int startNumListIdx = 0;
                do {
                  int delimiter = delimiters.get(0) - 1; //inclusive
                  int endIdx = delimiters.size() == 1 ? line.length() :
                          (getPrevCharOffset(line, offsets.get(startNumListIdx + delimiter + 1), 20, 30, offsets.get(startNumListIdx + delimiter)) + 1);
                  if (endIdx <= 0) {
                    logWriter.append("Failed to find list delimiter when formatting with Algorithm 1 line.");
                    break lw;
                  }

                  if (line.charAt(startIdx) != '　') { //chinese space character
                    tmpBuf.append('　');
                  }
                  tmpBuf.append(line.substring(startIdx, offsets.get(startNumListIdx)));
                  for (int i = 0; i <= delimiter; i++) {
                    int idx = offsets.get(startNumListIdx + i);
                    tmpBuf.append(LIST_MARK);
                    tmpBuf.append(NUMBER_NA_MARK); //safe to remove this debugging list order
                    if (i == 0) {
                      tmpBuf.append(LEADING_LIST_MARK);
                    }
                    int len = String.valueOf(numList.get(startNumListIdx + i)).length();
                    if (i == delimiter) { //last
                      tmpBuf.append(line.substring(idx + len + 1, endIdx) + "\n");
                    } else {
                      tmpBuf.append(line.substring(idx + len + 1, offsets.get(startNumListIdx + i + 1)));
                    }
                  }
                  startNumListIdx += delimiter + 1;
                  delimiters.remove(0);
                  startIdx = endIdx;
                } while (delimiters.size() > 0);

                tmpBuf.append("\n");
                formated = true;
                break;
              }

              if (formated) {
                resultWriter.append(tmpBuf.toString());
              }
            }

            //algorithm2: split by order like 1,2,3,1,2,3, check there is max-priority separator between 3,1
            if (!success) {
              success = true;
              List<Integer> borders = new ArrayList<>();
              int lastNum = 0;
              for (int i = 0; i < numList.size(); i++) {
                int num = numList.get(i);
                if (num > 1) {
                  if (num == lastNum + 1) {
                    lastNum = num;
                    continue;
                  } else {
                    success = false;
                  }
                } else {
                  if (lastNum > 1)
                    borders.add(i);
                }
                lastNum = num;
              }
              if (success) { //make sure there is a max-priority separator between each sections
                for (int border : borders) {
                  if (hasMaxPriorityChar(line, offsets.get(border - 1) + 2, offsets.get(border), max)) {
                    success = true;
                  } else {
                    success = false;
                    break;
                  }
                }
              }

              if (success) {
                hitAlgorithm2++;
                al2Titles.append(title + "\n");
              }
            }

            //algorithm 3  - 1, 1, 2, 3, 4, 5, 2, 3, 4
            if (!success) {
              success = true;
              Stack<Integer> s = new Stack<>();
              outer:
              for (int num : numList) {
                if (num == 1) {
                  s.push(num);
                } else {
                  if (s.size() == 0) {
                    success = false;
                    break;
                  } else {
                    if (num == s.peek() + 1) {
                      s.pop();
                      s.push(num);
                    } else {
                      do {
                        s.pop();
                        if (s.size() == 0) {
                          success = false;
                          break outer;
                        } else {
                          if (num == s.pop() + 1) {
                            s.push(num);
                            continue outer;
                          }
                        }
                      } while (s.size() > 0);
                      success = false;
                      break;
                    }
                  }
                }
              }
              if (success) {
                hitAlgorithm3++;
                al3Titles.append(title + ", list numbers: " + numList + "\n");
              }
            }

            //non-hit
            if (!success) {
              nonhit++;
              nonHitTitles.append(title + ", list numbers: " + numList + ", priorities: " + new ArrayList<Integer>(Arrays.asList(charPriority)) + "\n");
            }

          }
        }
      }

      if (!formated) {
        /*
        resultWriter.append(line.substring(0, offsets.get(0)) + "<br/>");
        resultWriter.append("<ol class=\"cols\" rel=\"2\">\n");
        for (int i = 0; i < offsets.size(); i++) {
          resultWriter.append("<li>");
          resultWriter.append(line.substring(offsets.get(i), i == offsets.size() - 1 ? line.length() : offsets.get(i + 1)));
          resultWriter.append("</li>");
        }
        resultWriter.append("</ol>");
        */

        String test = line.replace('．', '.');
        boolean found = false;
        int round = 0;
        List<Integer> listStartOffsets = new ArrayList<>();
        do {

          int startIdx = listStartOffsets.size() > 0 ? listStartOffsets.get(listStartOffsets.size() - 1) : 0;
          listStartOffsets.clear();
          int lastIdx = startIdx;
          for (int i = 1; i < 50; i++) {
            int idx = test.indexOf(String.valueOf(i) + ".", lastIdx);
            if (idx >= 0) {
              //filter bad index like 1813,
              do {
                char prevCh = test.charAt(idx - 1);
                if ((prevCh >= '0' && prevCh <= '9')
                        || (prevCh >= 'a' && prevCh <= 'z')
                        || (prevCh >= 'A' && prevCh <= 'Z')
                        || (prevCh == '.') || (prevCh == '-')) {  //invalid
                  lastIdx = idx + 2;
                  idx = test.indexOf(String.valueOf(i) + ".", lastIdx);
                  if (idx < 0) break;
                } else {//valid
                  break;
                }
              } while (true);
            }

            if (idx >= 0) {
              listStartOffsets.add(idx);
              lastIdx = idx + 2;
            } else {
              break;
            }
          }

          if (listStartOffsets.size() >= 2) {
            round++;
            found = true;
            //logWriter.append("List size: " + listStartOffsets.size() + ", content: " + line + "\n");

            resultWriter.append(line.substring(startIdx, listStartOffsets.get(0)));
            for (int i = 0; i < listStartOffsets.size(); i++) {
              int idx = listStartOffsets.get(i);
              resultWriter.append(LIST_MARK);
              int len = getListNumberLen(test, idx);
              resultWriter.append(line.substring(idx, idx + len) + "．"); //TODO
              if (i == listStartOffsets.size() - 1) { //last
                //resultWriter.append(line.substring(idx + len + 1));
                listStartOffsets.set(i, listStartOffsets.get(i) + len + 1);
              } else {
                resultWriter.append(line.substring(idx + len + 1, listStartOffsets.get(i + 1)));
              }

              char dotCh = line.charAt(idx + len);
              if (dotCh != '．') {
                //logWriter.write("Replacing [" + dotCh + "] with [．], content: ");
                if (i == listStartOffsets.size() - 1) { //last
                  logWriter.append(line.substring(idx));
                } else {
                  logWriter.append(line.substring(idx, listStartOffsets.get(i + 1)));
                }
                logWriter.write("\n");
              }
            }
            //resultWriter.append("\n");
          } else {
            found = false;
            resultWriter.append(line.substring(startIdx));
            listStartOffsets.clear();
          }
        } while (found);
        if (listStartOffsets.size() > 1) {
          logWriter.append(line.substring(listStartOffsets.get(listStartOffsets.size() - 1)));
        }

        resultWriter.write("\n");
      }
    }

  }

  private boolean hasMaxPriorityChar(String line, int start, int end, int max) {
    for (int i = start; i < end; i++) {
      if (getCharPriority(line.charAt(i)) == max)
        return true;
    }

    return false;
  }

  //:(40) > 。(30) > ;(20) > , (10)
  private int getCharPriority(char ch) {
    switch (ch) {
      case ':':
      case '：' :
        return 40;
      case '。':
        return 30;
      case ';':
      case '；':
        return 20;
      case ',':
      case '，':
        return 10;
      default:
        return 0;
    }
  }

  private int getPrevCharOffset(String line, int offset, int minPriority, int maxPriority, int min) {
    for (int i = offset; i >= min; i--) {
      int priority = getCharPriority(line.charAt(i));
      if (priority >= minPriority && priority <= maxPriority)
        return i;
    }
    return -1;
  }

  public static int getListNumberLen(String str, int idx) {
    for (int i = idx; i < str.length(); i++) {
      char ch = str.charAt(i);
      if (ch >= '0' && ch <= '9') {
        continue;
      } else {
        return i - idx;
      }
    }

    return -1;
  }

  static Pattern p = Pattern.compile("[^\\w\\.\\-](\\d+)\\.");
  private List<Integer> offsets = new ArrayList<>();
  private List<Integer> getNumberList(String line) throws IOException { // dot not replaced
    offsets.clear();
    String test = line.replace('．', '.');

    Matcher m = p.matcher(test);
    List<Integer> listNoList = new ArrayList<>();
    w: while (m.find()) {

      if (m.start(1) >= 1) {
        char prevCh = line.charAt(m.start(1) - 1);
        switch (prevCh) {
          case '、':
            continue w;
        }
      }

      if (m.start(1) >= 2) {
        if (line.substring(m.start(1) - 2, m.start(1)).equals("编号")) {
          continue;
        }
      }

      int number = Integer.parseInt(m.group(1));
      if (number >= 100) continue;

      listNoList.add(number);
      //check bad zero
      int offset = m.start(1);
      if (offset > 2) {
        if (line.charAt(offset - 1) == ' ' && line.charAt(offset - 2) == '0') {
          logWriter.append("Bad zero at line: " + line + "\n");
        }
      }
      offsets.add(m.start(1));
    }

    return listNoList;
  }

  private boolean isIncremental(List<Integer> list) {
    return list.size() == list.get(list.size() -1);
  }

  protected void postProcess() throws IOException {
    logWriter.append("Incremental list paragraphs: " + incremental + "\n");
    logWriter.append("Non-incremental list paragraphs: " + nonIncremental + "\n");
    logWriter.append("Split list hit AL 1: " + hitAlgorithm1 + "\n");
    logWriter.append("AL1 title list: " + al1Titles + "\n");
    logWriter.append("Split list hit AL 2: " + hitAlgorithm2 + "\n");
    logWriter.append("AL2 title list: " + al2Titles + "\n");
    logWriter.append("Split list hit AL 3: " + hitAlgorithm3 + "\n");
    logWriter.append("AL3 title list: " + al3Titles + "\n");
    logWriter.append("Split list hit AL 4: " + hitAlgorithm4 + "\n");
    logWriter.append("AL4 title list: " + al4Titles + "\n");

    logWriter.append("No-hit count: " + nonhit + "\n");
    logWriter.append("Non-hit title list: " + nonHitTitles + "\n");

    logWriter.append("Suspected source error count: " + srcErrorCount + "\n");
    logWriter.append("Suspected source error title list: " + srcErrorTitles + "\n");
  }

  /*
  model 1 - 1.text, 1.sub, 2.sub。 2.text, 1.sub, 2.sub //J．S．巴赫：法国组曲, 舒伯特：为钢琴而作的圆舞曲
  model 2 - 1.text。 1.sub。 2.sub。 2.text。 3.text。 //舒曼：第一交响曲
  model 3 - 1.text; 2.text; 3.text。1.text。 2.text。 3.text。 //贝多芬：艾格蒙特
  model 4 - a: 1.text。 2.text。 3.text。b: 1.text。 2.text。 3.text。 //J.S.巴赫：意大利协奏曲
   */
}
